package org.jboss.edt;

import java.util.ArrayList;
import java.util.List;

import javax.el.Expression;
import javax.persistence.EntityManager;

import org.hibernate.Criteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.richfaces.component.Filterable;
import org.richfaces.component.Sortable2;
import org.richfaces.model.DataProvider;
import org.richfaces.model.ExtendedFilterField;
import org.richfaces.model.FilterField;
import org.richfaces.model.Ordering;
import org.richfaces.model.SortField2;

@Name("PersonDataProvider")
@Scope(ScopeType.SESSION)
public class PersonDataProvider implements DataProvider<Person>, Sortable2,
		Filterable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	@In(create = true)
	private EntityManager entityManager;

	private List<FilterField> filterFields;
	private List<SortField2> sortFields;

	public Person getItemByKey(Object id) {
		return entityManager.find(Person.class, id);
	}

	public List<Person> getItemsByRange(int firstRow, int numberOfRows) {
		Criteria criteria = null;
		// Apply filtering rules
		criteria = filtersApplied();

		if (criteria == null) {
			// No filter -> normal selection by range
			criteria = getCriteria().setFirstResult(firstRow).setMaxResults(
					numberOfRows);
		} else {
			// Filtering rules applied, limit records retrieved
			criteria.setFirstResult(firstRow).setMaxResults(numberOfRows);
		}

		// Apply sorting rules
		sort(criteria);
		List<Person> users = (ArrayList<Person>) criteria.list();
		return users;
	}

	private void sort(Criteria criteria) {
		boolean defaultSorting = true;
		for (SortField2 sf : sortFields) {
			if (sf != null) {
				defaultSorting = false;
				Ordering ordering = sf.getOrdering();
				Expression expression = sf.getExpression();
				switch (ordering) {
				case ASCENDING:
					criteria.addOrder(Order
							.asc(getColumnFromExpression(expression)));
					break;
				case DESCENDING:
					criteria.addOrder(Order
							.desc(getColumnFromExpression(expression)));
					break;
				}
			}
		}

		if (defaultSorting) {
			criteria.addOrder(Order.asc("id"));
		}
	}

	private Criteria filtersApplied() {
		if (filterFields == null || filterFields.isEmpty()) {
			return null;
		}
		Criteria criteria = null;
		for (FilterField ff : filterFields) {
			ExtendedFilterField eff = (ExtendedFilterField) ff;
			if (eff.getFilterValue() != null) {
				// Filter found
				if (criteria == null) {
					criteria = getCriteria();
				}
				criteria.add(Restrictions.like(
						getColumnFromExpression(ff.getExpression()),
						String.format("%%%s%%", eff.getFilterValue())));
			}
		}
		return criteria;
	}

	/**
	 * @param expression
	 *            The expression to be parsed to get the name of the property.
	 * @return Property name of the entity.
	 */
	private String getColumnFromExpression(Expression expression) {
		// #{_person.first}
		String expressionString = expression.getExpressionString();
		int length = expressionString.length();
		int dot = expressionString.lastIndexOf(".");
		return expressionString.substring(dot + 1, length - 1);
	}

	private Criteria getCriteria() {
		return ((org.hibernate.Session) entityManager.getDelegate())
				.createCriteria(Person.class);
	}

	public Object getKey(Person person) {
		return person.getId();
	}

	public int getRowCount() {
		Criteria criteria;
		criteria = filtersApplied();

		if (criteria == null) {
			// No filter -> Count rows as such
			criteria = getCriteria();
		}

		criteria.setProjection(Projections.rowCount());
		Integer count = ((Long) criteria.uniqueResult()).intValue();
		return count;
	}

	public List<SortField2> getSortFields() {
		return sortFields;
	}

	public void setSortFields(List<SortField2> sortFields) {
		this.sortFields = sortFields;
	}

	public List<FilterField> getFilterFields() {
		return filterFields;
	}

	public void setFilterFields(List<FilterField> filterFields) {
		this.filterFields = filterFields;
	}

}